#ifndef INCLUDED_NTENGINE_GRAPHICS_MANAGERIMPL_H
#define INCLUDED_NTENGINE_GRAPHICS_MANAGERIMPL_H

#include "NTEngine/Graphics/Manager.h"
#include "NTEngine/Core/Core.h"
#include "NTEngine/Threading/Manager.h"
#include "NTEngine/Threading/Function.h"
#include "NTEngine/Math/Matrix4x4.h"
#include "NTEngine/Math/Matrix3x4.h"
#include "NTEngine/Math/Vector3.h"
#include "NTEngine/Math/Vector4.h"

#include "Base/src/android_app.h"

#include <android/native_activity.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <string.h>

#ifndef NDEBUG
// テスト用
#include <android/log.h>
#ifdef LOGI
#undef LOGI
#endif
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "Graphics", __VA_ARGS__))
#else
#define LOGI(...)
#endif

//#define TEST_FRAME_BUFFER // フレームバッファのテスト

#include "Graphics/src/VertexBufferImpl.h"
#include "Graphics/src/IndexBufferImpl.h"
#include "Graphics/src/TextureImpl.h"

namespace NTEngine {
namespace Graphics {

class ManagerImpl {
public:
	ManagerImpl(
		android_app* p,
		bool enableDepthStencil,
		bool enableAntialias,
		bool enableAnisotropicFilter,
		bool translucent )
	: mWindowHandle( p )
	, mDisplay( EGL_NO_DISPLAY )
	, mSurface( EGL_NO_SURFACE )
	, mContext( EGL_NO_CONTEXT )
	, mConfig( 0 )
	, mWidth( 0 )
	, mHeight( 0 )
	, mCanRender( false )
	, mEnableDepthStencil( enableDepthStencil )
	, mEnableAntialias( enableAntialias )
	, mEnableAnisotropicFilter( enableAnisotropicFilter )
	, mTranslucent( translucent )
	, mBitField( GL_COLOR_BUFFER_BIT )
	, mVertexLightingShader( 0 )
	, mPixelLightingShader( 0 )
	, mNoLightingShader( 0 )
	, mCurrentVertexBuffer( 0 )
	, mCurrentIndexBuffer( 0 )
	, mCurrentTexture( 0 )
	, mCurrentMagFilter( GL_NEAREST )
	, mCurrentMinFilter( GL_NEAREST_MIPMAP_NEAREST )
	, mBestMagFilter( GL_LINEAR )
	, mBestMinFilter( GL_LINEAR_MIPMAP_LINEAR )
	, mTextureFilter( TEXTURE_FILTER_POINT )
	, mMaxAnisotropy( 1.0f )
	, mMatricesChanged( true )
	, mLightPositionXs( 0.f )
	, mLightPositionYs( 0.f )
	, mLightPositionZs( 0.f )
	, mEyePosition( 0.f, 0.f, 0.f )
	, mDiffuseColor( 1.f )
	, mSpecularColor( 1.f )
	, mEmissionColor( 0.f )
	, mAmbientColor( 0.f )
	, mLightChanged( true )
	, mCurrentDepthTest( true )
	, mCurrentDepthWrite( GL_TRUE )
	, mCurrentCullFace( GL_NONE )
	, mCurrentSrcBlend( GL_ONE )
	, mCurrentDstBlend( GL_ZERO )
	, mLightingMode( LIGHTING_NONE )
	, mWhiteTexture( 0 )
	, mFrameId( 0 )
#ifdef TEST_FRAME_BUFFER
	, mFrameBuffer( 0 )
	, mRenderBuffer( 0 )
	, mDepthTexture( 0 )
#endif
	{
		// 初期化リストで初期化できないものの初期化
		// コンストラクタではOpenGLESの初期化ができないので、数値の初期化だけしておく
		memset( &mViewport, 0, sizeof( mViewport ) );
		memset( &mLocation, 0xff, sizeof( mLocation ) );
		for ( int i = 0; i < 4; ++i ) {
			mLightColors[ i ].set( 0.f, 0.f, 0.f );
			mLightIntensities[ i ] = 0.f;
		}
		mProjectionViewMatrix.identity();
		mWorldMatrix.identity();
	}

	~ManagerImpl() {
		// 二重開放にならないようにチェック
		if ( mCanRender ) {
			release();
		}
		mWindowHandle = NULL;
	}

	void enableDepthTest( bool f ) {
		if ( f == mCurrentDepthTest ) {
			return;
		}
		if ( f ) {
			glEnable( GL_DEPTH_TEST );
		} else {
			glDisable( GL_DEPTH_TEST );
		}
		mCurrentDepthTest = f;
	}

	void enableDepthWrite( bool f ) {
		GLboolean tf = f ? GL_TRUE : GL_FALSE;
		if ( tf == mCurrentDepthWrite ) {
			return;
		}
		glDepthMask( tf );
		mCurrentDepthWrite = tf;
	}

	void setBlendMode( BlendMode b ) {
		GLenum srcBlend, dstBlend;
		switch ( b ) {
		case BLEND_LINEAR:
			srcBlend = GL_SRC_ALPHA;
			dstBlend = GL_ONE_MINUS_SRC_ALPHA;
			break;
		case BLEND_ADDITIVE:
			srcBlend = GL_SRC_ALPHA;
			dstBlend = GL_ONE;
			break;
		case BLEND_OPAQUE:
			srcBlend = GL_ONE;
			dstBlend = GL_ZERO;
			break;
		default: // これはありえない
			HALT( b );
		}
		if (
			( srcBlend != mCurrentSrcBlend ) ||
			( dstBlend != mCurrentDstBlend )
		) {
			glBlendFunc( srcBlend, dstBlend );
			mCurrentSrcBlend = srcBlend;
			mCurrentDstBlend = dstBlend;
		}
	}

	void setCullMode( CullMode c ) {
		GLenum cullFace;
		switch ( c ) {
		case CULL_BACK:
			cullFace = GL_BACK;
			break;
		case CULL_FRONT:
			cullFace = GL_FRONT;
			break;
		case CULL_NONE:
			cullFace = GL_NONE;
			break;
		default: // これはありえない
			HALT( c );
		}
		if ( cullFace != mCurrentCullFace ) {
			if ( cullFace ) {
				glEnable( GL_CULL_FACE );
				glCullFace( cullFace );
			} else {
				glDisable( GL_CULL_FACE );
			}
			mCurrentCullFace = cullFace;
		}
	}

	void setTextureFilter( TextureFilter f ) {
		if ( f == mTextureFilter ) {
			return;
		}
		mTextureFilter = f;
	}

	void setVertexBuffer( VertexBuffer::Impl* a ) {
		if ( mCurrentVertexBuffer != a ) {
			// 現在のバッファを開放
			if ( ( mCurrentVertexBuffer ) && ( mCurrentVertexBuffer->unref() <= 0 ) ) {
				SAFE_DELETE( mCurrentVertexBuffer );
			}
			// 新しいバッファを設定
			mCurrentVertexBuffer = a;
			if ( a ) {
				a->refer();
			}
		}
		if ( mCurrentVertexBuffer ) {
			glBindBuffer( GL_ARRAY_BUFFER, mCurrentVertexBuffer->mObject );
		} else {
			glBindBuffer( GL_ARRAY_BUFFER, 0 );
		}
	}

	void setIndexBuffer( IndexBuffer::Impl* a ) {
		if ( mCurrentIndexBuffer != a ) {
			// 現在のバッファを開放
			if ( ( mCurrentIndexBuffer ) && ( mCurrentIndexBuffer->unref() <= 0 ) ) {
				SAFE_DELETE( mCurrentIndexBuffer );
			}
			// 新しいバッファを設定
			mCurrentIndexBuffer = a;
			if ( a ) {
				a->refer();
			}
		}
		if ( mCurrentIndexBuffer ) {
			glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mCurrentIndexBuffer->mObject );
		} else {
			glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
		}
	}

	void setTexture( Texture::Impl* a ) {
		if ( mCurrentTexture != a ) {
			if ( ( mCurrentTexture ) && ( mCurrentTexture->unref() <= 0 ) ) {
				SAFE_DELETE( mCurrentTexture );
			}
			mCurrentTexture = a;
			if ( a ) {
				a->refer();
			}
		}
		if ( mCurrentTexture ) {
			glActiveTexture( GL_TEXTURE0 );
			glBindTexture( GL_TEXTURE_2D, mCurrentTexture->mObject );
			glUniform1i( mLocation.texture, 0 ); // サンプラステージに登録
		} else {
			glBindTexture( GL_TEXTURE_2D, 0 );
		}
	}

	void setProjectionViewMatrix( const Matrix4x4& m ) {
		if ( mProjectionViewMatrix != m ) {
			mProjectionViewMatrix = m;
			mMatricesChanged = true;
		}
	}

	void setWorldMatrix( const Matrix3x4& m ) {
		if ( mWorldMatrix != m ) {
			mWorldMatrix = m;
			mMatricesChanged = true;
		}
	}

	void sendMatrices() {
		// 最終変換行列
		Matrix4x4 pvwm;
		pvwm.setMul( mProjectionViewMatrix, mWorldMatrix );
		glUniform4fv( mLocation.transformMatrix, 4, &( pvwm.m11 ) );
		// ライティングなしはここまで、この先はライティングありの場合だけ
		if ( mLightingMode != LIGHTING_NONE ) {
			// 法線変換用ワールド逆行列の転置行列
			glUniform4fv( mLocation.worldMatrix, 3, &( mWorldMatrix.m11 ) );
			Matrix3x4 itwm;
			itwm.setInverse( mWorldMatrix );
			itwm.transpose33();
			glUniform4fv( mLocation.itWorldMatrix, 3, &( itwm.m11 ) );
		}
		// 転送完了
		mMatricesChanged = false;
	}

	void setLightingMode( LightingMode m ) {
		if ( m != mLightingMode ) {
			mMatricesChanged = true;
			mLightChanged = true;
			mLightingMode = m;
			setShaderProgram();
		}
	}

	void setLightPosition( int idx, const Vector3& pos ) {
		mLightPositionXs[ idx ] = pos.x;
		mLightPositionYs[ idx ] = pos.y;
		mLightPositionZs[ idx ] = pos.z;
		mLightChanged = true;
	}

	void setLightIntensity( int idx, float intensity ) {
		mLightIntensities[ idx ] = intensity;
		mLightChanged = true;
	}

	void setLightColor( int idx, const Vector3& color ) {
		mLightColors[ idx ] = color;
		mLightChanged = true;
	}

	void setEyePosition( const Vector3& pos ) {
		mEyePosition = pos;
		mLightChanged = true;
	}

	void setDiffuseColor( const Vector3& c ) {
		mDiffuseColor.x = c.x;
		mDiffuseColor.y = c.y;
		mDiffuseColor.z = c.z;
		mLightChanged = true;
	}

	void setSpecularColor( const Vector3& c ) {
		mSpecularColor.x = c.x;
		mSpecularColor.y = c.y;
		mSpecularColor.z = c.z;
		mLightChanged = true;
	}

	void setEmissionColor( const Vector3& c ) {
		mEmissionColor.x = c.x;
		mEmissionColor.y = c.y;
		mEmissionColor.z = c.z;
		mLightChanged = true;
	}

	void setAmbientColor( const Vector3& c ) {
		mAmbientColor.x = c.x;
		mAmbientColor.y = c.y;
		mAmbientColor.z = c.z;
		mLightChanged = true;
	}

	void setTransparency( float t ) {
		mDiffuseColor.w = t;
		mLightChanged = true;
	}

	void setSpecularSharpness( float e ) {
		mSpecularColor.w = e;
		mLightChanged = true;
	}

	void sendLightingParameters() {
		// Diffuseは共通
		glUniform4fv( mLocation.diffuse, 1, &mDiffuseColor.x );
		if ( mLightingMode != LIGHTING_NONE ) {
			Vector3 lc[ 4 ];
			for ( int i = 0; i < 4; ++i ) {
				lc[ i ].x = mLightColors[ i ].x * mLightIntensities[ i ];
				lc[ i ].y = mLightColors[ i ].y * mLightIntensities[ i ];
				lc[ i ].z = mLightColors[ i ].z * mLightIntensities[ i ];
				//lc[ i ].w = 0.f;
			}
			glUniform3fv( mLocation.ambient, 1, &mAmbientColor.x );
			glUniform4fv( mLocation.lightPosX, 1, &mLightPositionXs.x );
			glUniform4fv( mLocation.lightPosY, 1, &mLightPositionYs.x );
			glUniform4fv( mLocation.lightPosZ, 1, &mLightPositionZs.x );
			glUniform3fv( mLocation.lightColor0, 1, &lc[ 0 ].x );
			glUniform3fv( mLocation.lightColor1, 1, &lc[ 1 ].x );
			glUniform3fv( mLocation.lightColor2, 1, &lc[ 2 ].x );
			glUniform3fv( mLocation.lightColor3, 1, &lc[ 3 ].x );
			if ( mLightingMode == LIGHTING_PER_VERTEX ) {
				// スペキュラ色は強度を調整2+e/2pi
				Vector4 sc;
				static const float rcpPI2 = 1.f / ( 3.1415926535897932384626433832795f * 2.f );
				float f = ( 2.f + mSpecularColor.w ) * rcpPI2;
				sc.x = mSpecularColor.x * f;
				sc.y = mSpecularColor.y * f;
				sc.z = mSpecularColor.z * f;
				sc.w = mSpecularColor.w;
				glUniform4fv( mLocation.specular, 1, &sc.x );
				glUniform3fv( mLocation.emission, 1, &mEmissionColor.x );
				glUniform3fv( mLocation.eyePos, 1, &mEyePosition.x );
			}
		}
		mLightChanged = false;
	}

	void preDraw() {
		if ( mMatricesChanged ) {
			sendMatrices();
		}
		if ( mLightChanged ) {
			sendLightingParameters();
		}
		if ( !mCurrentTexture ) {
			setTexture( mWhiteTexture );
		}
		// テクスチャサンプラステージはここで登録する
		GLint magF, minF;
		if ( mTextureFilter == TEXTURE_FILTER_POINT ) {
			magF = GL_NEAREST;
			minF = ( mCurrentTexture->hasMipmapChain() ) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
		} else if ( mTextureFilter == TEXTURE_FILTER_LINEAR ) {
			magF = GL_LINEAR;//mBestMagFilter;
			minF = ( mCurrentTexture->hasMipmapChain() ) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR;
		} else {
			HALT( mTextureFilter );
		}
		if ( mEnableAnisotropicFilter ) {
			glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, mMaxAnisotropy );
		}
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magF );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minF );
		ASSERT( mCurrentVertexBuffer );
		mCurrentVertexBuffer->setInputLayout();
	}

	void draw( int first, int count, GLenum mode ) {
		preDraw();
		glDrawArrays( mode, first, count );
	}

	void drawIndexed( int first, int count, GLenum mode ) {
		preDraw();
		ASSERT( mCurrentIndexBuffer );
		ASSERT( count <= mCurrentIndexBuffer->mIndexNumber );
		glDrawElements( mode, count, GL_UNSIGNED_SHORT, static_cast< unsigned short* >( 0 ) + first );
	}

	void setViewport( int x, int y, int w, int h ) {
		mViewport.x = x;
		mViewport.y = y;
		mViewport.width = w;
		mViewport.height = h;
	}

	void getViewport( int* x, int* y, int* w, int* h ) {
		if ( x ) { *x = mViewport.x; }
		if ( y ) { *y = mViewport.y; }
		if ( w ) { *w = mViewport.width; }
		if ( h ) { *h = mViewport.height; }
	}

	void beginRender() {
		// 全画面のビューポートを設定
		glViewport( 0, 0, mWidth, mHeight );
		// バッファをクリア
		glClear( mBitField );
		// ユーザ定義のビューポートを設定
	}

	void endRender() {
		// バッファ交換
		EGLint result;
		if ( Threading::Manager().getCoreNumber() == 1 ) {
			// 1コアの場合は他のものにも処理をまわすため、スワップが1ms未満であればスリープを要求します
			Core app;
			unsigned st = app.time();
			eglSwapBuffers( mDisplay, mSurface );
			result = eglGetError();
			unsigned et = app.time();
			if ( st == et ) {
				Threading::sleep( 0 ); // 他のことをやってみる
			}
		} else {
			// 2コア以上ならそのままスワップします
			eglSwapBuffers( mDisplay, mSurface );
			result = eglGetError();
		}
		// 常にEGL_CONTEXT_LOSTかどうかを判定します
		// EGLコンテキストに関連付けられたデータが失われてしまうので
		// 状態の開放を行い、次のウィンドウが生成されるまでスリープする必要があるためです
		if ( result == EGL_CONTEXT_LOST ) {
			release();
		}

		++mFrameId; //フレーム番号インクリメント
	}

	void restore() {
		// 生成済みもしくはウィンドウハンドルがNULLなら何もしない
		if ( mCanRender || ( mWindowHandle->window == NULL ) ) {
			return;
		}
		// Displayゲット
		mDisplay = eglGetDisplay( EGL_DEFAULT_DISPLAY );
		HARD_ASSERT( mDisplay != EGL_NO_DISPLAY );
		// EGL初期化
		EGLBoolean result;
		result = eglInitialize( mDisplay, NULL, NULL );
		HARD_ASSERT( result == EGL_TRUE );
		// ベストなConfigを選ぶ
		if ( mConfig == 0 ) {
			mConfig = chooseBestConfig( mDisplay, mEnableDepthStencil, mEnableAntialias, mTranslucent );
			HARD_ASSERT( mConfig );
		}
#ifndef NDEBUG
		printConfig( mDisplay, mConfig );
#endif

		if ( mEnableDepthStencil ) {
			mBitField |= GL_DEPTH_BUFFER_BIT;
			EGLint stencil;
			eglGetConfigAttrib( mDisplay, mConfig, EGL_STENCIL_SIZE, &stencil );
			if ( stencil > 0 ) {
				mBitField |= GL_STENCIL_BUFFER_BIT;
			}
		}
		// Android用の設定。WindowのフォーマットIDを取得して設定する
		EGLint format;
		eglGetConfigAttrib( mDisplay, mConfig, EGL_NATIVE_VISUAL_ID, &format );
		ANativeWindow_setBuffersGeometry( mWindowHandle->window, 0, 0, format );
		// レンダーサーフェースを生成
		const EGLint surfaceAttribs[] ={
			EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
			EGL_NONE,
		};
		mSurface = eglCreateWindowSurface( mDisplay, mConfig, mWindowHandle->window, surfaceAttribs );
		HARD_ASSERT( mSurface != EGL_NO_SURFACE );
		// コンテキストの生成
		const EGLint contextAttribs[] = {
			EGL_CONTEXT_CLIENT_VERSION, 2,
			EGL_NONE
		};
		mContext = eglCreateContext( mDisplay, mConfig, EGL_NO_CONTEXT, contextAttribs );
		HARD_ASSERT( mContext != EGL_NO_CONTEXT );
		// 上記の設定でカレントに設定
		result = eglMakeCurrent( mDisplay, mSurface, mSurface, mContext );
		HARD_ASSERT( result == EGL_TRUE );
		// 生成完了。バージョン情報などをみておく
		LOGI( "GL_VENDOR : %s\n", glGetString( GL_VENDOR ) );
		LOGI( "GL_RENDERER : %s\n", glGetString( GL_RENDERER ) );
		LOGI( "GL_VERSION : %s\n", glGetString( GL_VERSION ) );
		LOGI( "GL_SHADING_LANGUAGE_VERSION : %s\n", glGetString( GL_SHADING_LANGUAGE_VERSION ) );
		// ディスプレイサイズ取得
		changeDisplayMode();
		// 異方性フィルタチェック
		if ( !isExtensionSupported( "GL_EXT_texture_filter_anisotropic" ) ) {
			// サポートしてないので、強制的に
			mEnableAnisotropicFilter = false;
			LOGI( "GL_EXTENSIONS : %s\n", glGetString( GL_EXTENSIONS ) );
		}
		if ( mEnableAnisotropicFilter ) {
			glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &mMaxAnisotropy );
		}
		// ホワイトテクスチャ生成
		mWhiteTexture = NEW Texture::Impl( 1, 1, false );
		unsigned* sureface;
		int pitch;
		mWhiteTexture->lock( &sureface, &pitch, 0 );
		sureface[ 0 ] = 0xffffffff;
		mWhiteTexture->unlock( 0 );
		// シェーダの初期化
		initShader();
		// 必要なステートの初期化
		initRenderState();
		// 描画開始OK.
		mCanRender = true;
	}

	void release() {
		// 各バッファをリセット
		setVertexBuffer( 0 );
		setIndexBuffer( 0 );
		setTexture( 0 );
		// ホワイトテクスチャ開放
		mWhiteTexture->unref();
		SAFE_DELETE( mWhiteTexture );
// 長いのでdefineマクロを...
#define SAFE_DELETE_SHADER_PROGRAM( shader_object ) \
if ( shader_object ) {\
	glDeleteProgram( shader_object );\
	shader_object = 0;\
}
		SAFE_DELETE_SHADER_PROGRAM( mVertexLightingShader );
		SAFE_DELETE_SHADER_PROGRAM( mPixelLightingShader );
		SAFE_DELETE_SHADER_PROGRAM( mNoLightingShader );
#undef SAFE_DELETE_SHADER_PROGRAM
		if ( mSurface != EGL_NO_SURFACE ) {
			eglMakeCurrent( mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
			eglDestroySurface( mDisplay, mSurface );
			mSurface = EGL_NO_SURFACE;
		}
		if ( mContext != EGL_NO_CONTEXT) {
			eglDestroyContext( mDisplay, mContext );
			mContext = EGL_NO_CONTEXT;
		}
		if ( mDisplay != EGL_NO_DISPLAY ) {
			eglTerminate( mDisplay );
			mDisplay = EGL_NO_DISPLAY;
		}
		mConfig = 0;

		mCanRender = false;
	}

	void changeDisplayMode() {
		eglQuerySurface( mDisplay, mSurface, EGL_WIDTH, &mWidth );
		eglQuerySurface( mDisplay, mSurface, EGL_HEIGHT, &mHeight );
		mViewport.width = mWidth;
		mViewport.height = mHeight;
	}

	void initRenderState() {
		// グローバルステート
#ifdef NDEBUG
		glClearColor( 0.f, 0.f, 0.0f, 1.f );
#else
		glClearColor( 0.39f, 0.58f, 0.93f, 1.f ); // ライティングの失敗をわかりやすくするため
#endif
		glFrontFace( GL_CCW );
		glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
		if ( mCurrentDepthTest ) {
			glEnable( GL_DEPTH_TEST );
		} else {
			glDisable( GL_DEPTH_TEST );
		}
		glDepthMask( mCurrentDepthWrite );
		glEnable( GL_BLEND );
		glBlendFunc( mCurrentSrcBlend, mCurrentDstBlend );
		if ( mCurrentCullFace ) {
			glEnable( GL_CULL_FACE );
			glCullFace( mCurrentCullFace );
		} else {
			glDisable( GL_CULL_FACE );
		}

		// 初回はシェーダ情報おくってくれ
		mMatricesChanged = true;
		mLightChanged = true;
	}

	bool canRender() const {
		return mCanRender;
	}

#ifdef TEST_FRAME_BUFFER
	void initFrameBuffer( GLsizei w, GLsizei h ) {
		// デプステクスチャ
		glGenTextures( 1, &mDepthTexture );
		glBindTexture( GL_TEXTURE_2D, mDepthTexture );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0 ); // extで場合分けが必要だな。。。
		glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_HALF_FLOAT_OES, 0 ); // extで場合分けが必要だな。。。
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glBindTexture( GL_TEXTURE_2D, 0 );
		// レンダリングバッファ
		glGenRenderbuffers( 1, &mRenderBuffer );
		glBindRenderbuffer( GL_RENDERBUFFER, mRenderBuffer );
		glRenderbufferStorage( GL_RENDERBUFFER, GL_RGB565, w, h );
		glBindRenderbuffer( GL_RENDERBUFFER, 0 );
		// フレームバッファ
		glGenFramebuffers( 1, &mFrameBuffer );
		glBindFramebuffer( GL_FRAMEBUFFER, mFrameBuffer );
		glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mRenderBuffer );
		glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, mDepthTexture, 0 );
		glBindFramebuffer( GL_FRAMEBUFFER, 0 );
	}
#endif

	// シェーダの初期化を行う
	void initShader() {
		// シェーダオブジェクトを生成
		GLuint vsNoLighting = createShader( GL_VERTEX_SHADER, sShaderNoLightingVsSrc );
		GLuint vsVertexLighting = createShader( GL_VERTEX_SHADER, sShaderVertexLightingVsSrc );
		GLuint vsPixelLighting = createShader( GL_VERTEX_SHADER, sShaderPixelLightingVsSrc );
		GLuint fsPixelLighting = createShader( GL_FRAGMENT_SHADER, sShaderPixelLightingFsSrc );
		GLuint fsBasic = createShader( GL_FRAGMENT_SHADER, sShaderBasicFsSrc );
		// 各エフェクトごとのシェーダプログラムオブジェクトを設定
		GLuint programObject = createShaderProgram( vsNoLighting, fsBasic );
		glBindAttribLocation( programObject, 0, "POSITION0" );
		glBindAttribLocation( programObject, 1, "COLOR0" );
		glBindAttribLocation( programObject, 2, "TEXCOORD0" );
		mNoLightingShader = linkProgram( programObject );
		// 頂点ベースライティング
		programObject = createShaderProgram( vsVertexLighting, fsBasic );
		glBindAttribLocation( programObject, 0, "POSITION0" );
		glBindAttribLocation( programObject, 1, "COLOR0" );
		glBindAttribLocation( programObject, 2, "TEXCOORD0" );
		glBindAttribLocation( programObject, 3, "NORMAL0" );
		mVertexLightingShader = linkProgram( programObject );
		// ピクセルベースライティング
		programObject = createShaderProgram( vsPixelLighting, fsPixelLighting );
		glBindAttribLocation( programObject, 0, "POSITION0" );
		glBindAttribLocation( programObject, 1, "COLOR0" );
		glBindAttribLocation( programObject, 2, "TEXCOORD0" );
		glBindAttribLocation( programObject, 3, "NORMAL0" );
		mPixelLightingShader = linkProgram( programObject );

		programObject = 0;
		setShaderProgram();
	}

	void setShaderProgram() {
		// TODO:セットするたびに取得しなおしているが、初期化時に一度だけ取得してあとは付け替えるだけのほうがよいかもしれない。
		switch ( mLightingMode ) {
		case LIGHTING_PER_VERTEX:
			glUseProgram( mVertexLightingShader );
			glEnableVertexAttribArray( 0 );
			glEnableVertexAttribArray( 1 );
			glEnableVertexAttribArray( 2 );
			glEnableVertexAttribArray( 3 );
			// Uniform位置を確認
			mLocation.transformMatrix = glGetUniformLocation( mVertexLightingShader, "gTransform" );
			mLocation.worldMatrix = glGetUniformLocation( mVertexLightingShader, "gWorldMatrix" );
			mLocation.itWorldMatrix = glGetUniformLocation( mVertexLightingShader, "gInvTransposedWorldMatrix" );
			mLocation.diffuse = glGetUniformLocation( mVertexLightingShader, "gDiffuseColor" );
			mLocation.ambient = glGetUniformLocation( mVertexLightingShader, "gAmbient" );
			mLocation.lightPosX = glGetUniformLocation( mVertexLightingShader, "gLightPosX" );
			mLocation.lightPosY = glGetUniformLocation( mVertexLightingShader, "gLightPosY" );
			mLocation.lightPosZ = glGetUniformLocation( mVertexLightingShader, "gLightPosZ" );
			mLocation.lightColor0 = glGetUniformLocation( mVertexLightingShader, "gLightColor0" );
			mLocation.lightColor1 = glGetUniformLocation( mVertexLightingShader, "gLightColor1" );
			mLocation.lightColor2 = glGetUniformLocation( mVertexLightingShader, "gLightColor2" );
			mLocation.lightColor3 = glGetUniformLocation( mVertexLightingShader, "gLightColor3" );
			mLocation.texture = glGetUniformLocation( mVertexLightingShader, "gTexture" );
			break;
		case LIGHTING_PER_PIXEL:
			glUseProgram( mPixelLightingShader );
			glEnableVertexAttribArray( 0 );
			glEnableVertexAttribArray( 1 );
			glEnableVertexAttribArray( 2 );
			glEnableVertexAttribArray( 3 );
			// Uniform位置を確認
			mLocation.transformMatrix = glGetUniformLocation( mPixelLightingShader, "gTransform" );
			mLocation.worldMatrix = glGetUniformLocation( mPixelLightingShader, "gWorldMatrix" );
			mLocation.itWorldMatrix = glGetUniformLocation( mPixelLightingShader, "gInvTransposedWorldMatrix" );
			mLocation.diffuse = glGetUniformLocation( mPixelLightingShader, "gDiffuseColor" );
			mLocation.ambient = glGetUniformLocation( mPixelLightingShader, "gAmbient" );
			mLocation.emission = glGetUniformLocation( mPixelLightingShader, "gEmissionColor" );
			mLocation.specular = glGetUniformLocation( mPixelLightingShader, "gSpecularColor" );
			mLocation.lightPosX = glGetUniformLocation( mPixelLightingShader, "gLightPosX" );
			mLocation.lightPosY = glGetUniformLocation( mPixelLightingShader, "gLightPosY" );
			mLocation.lightPosZ = glGetUniformLocation( mPixelLightingShader, "gLightPosZ" );
			mLocation.eyePos  = glGetUniformLocation( mPixelLightingShader, "gEyePosition" );
			mLocation.lightColor0 = glGetUniformLocation( mPixelLightingShader, "gLightColor0" );
			mLocation.lightColor1 = glGetUniformLocation( mPixelLightingShader, "gLightColor1" );
			mLocation.lightColor2 = glGetUniformLocation( mPixelLightingShader, "gLightColor2" );
			mLocation.lightColor3 = glGetUniformLocation( mPixelLightingShader, "gLightColor3" );
			mLocation.texture = glGetUniformLocation( mPixelLightingShader, "gSampler" );
			break;
		case LIGHTING_NONE:
			glUseProgram( mNoLightingShader );
			glEnableVertexAttribArray( 0 );
			glEnableVertexAttribArray( 1 );
			glEnableVertexAttribArray( 2 );
			glDisableVertexAttribArray( 3 );
			// Uniform位置を確認
			mLocation.transformMatrix = glGetUniformLocation( mNoLightingShader, "gTransform" );
			mLocation.diffuse = glGetUniformLocation( mNoLightingShader, "gDiffuseColor" );
			mLocation.texture = glGetUniformLocation( mNoLightingShader, "gTexture" );
			break;
		default:
			ASSERT( false );
			break;
		}
	}

	// シェーダプログラムのリンク
	static GLuint linkProgram( GLuint programObject ) {
		glLinkProgram( programObject );
		GLint linked;
		glGetProgramiv( programObject, GL_LINK_STATUS, &linked );
		if ( !linked ) {
			GLint infoLen = 0;
			glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );
			if ( infoLen > 1 ) {
				char* infoLog = NEW char[ infoLen ];
				glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
				LOGI( "------shader program error %s.------", infoLog );
				SAFE_DELETE_ARRAY( infoLog );
			}
			glDeleteProgram( programObject );
			programObject = 0;
		}
		return programObject;
	}

	// シェーダプログラムの生成
	static GLuint createShaderProgram( GLuint vs, GLuint fs ) {
		GLuint programObject = glCreateProgram();
		glAttachShader( programObject, vs );
		glAttachShader( programObject, fs );
		return programObject;
	}

	// シェーダオブジェクトの生成
	static GLuint createShader( GLenum type, const char* shaderSrc ) {
		// オブジェクトの生成とコンパイル
		GLuint shader = glCreateShader( type );
		if ( shader == 0 ) {
			return 0;
		}
		glShaderSource( shader, 1, &shaderSrc, NULL );
		glCompileShader( shader );

		// コンパイル結果のチェック
		GLint compiled;
		glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled );
		if ( !compiled ) {
			GLint infoLen = 0;
			glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &infoLen );
			if ( infoLen > 1 ) {
				char* infoLog = NEW char[ infoLen ];
				glGetShaderInfoLog( shader, infoLen, NULL, infoLog );
				LOGI( "------shader error %s.------", infoLog );
				SAFE_DELETE_ARRAY( infoLog );
			}
			glDeleteShader( shader );
			shader = 0;
		}
		return shader;
	}

	// OpenGLES Extension のサポートチェック
	static bool isExtensionSupported( const char* targetExtension ) {
		GLubyte* where = reinterpret_cast< GLubyte* >( strchr( targetExtension, ' ' ) );
		if ( where || *targetExtension == '\0' ) {
			return false;
		}
		// 文字列の中に必要な extension があるか調べる
		const GLubyte* extensions = glGetString( GL_EXTENSIONS );
		const GLubyte* start = extensions;
		while ( true ) {
			// 拡張名は他の拡張名の接頭語になりうるので、strstr()の利用のみでは十分ではない
			where = reinterpret_cast< GLubyte* >( strstr( reinterpret_cast< const char* >( start ), targetExtension ) );
			if ( !where ) {
				break;
			}
			// そこで長さのチェックもやっておく
			GLubyte* terminator = where + strlen( targetExtension );
			if ( where == start || *( where - 1 ) == ' ' ) {
				if ( *terminator == ' ' || *terminator == '\0' ) {
					return true;
				}
			}
			start = terminator;
		}
		return false;
	}

	// もっともリッチなConfigを選択する
	static EGLConfig chooseBestConfig( EGLDisplay display, bool enableDepthStencil, bool enableAntialias, bool translucent ) {
		// 希望数値
		const EGLint requestRedSize = ( translucent ) ? 8 : 5;
		const EGLint requestGreenSize = ( translucent ) ? 8 : 6;
		const EGLint requestBlueSize = ( translucent ) ? 8 : 5;
		const EGLint requestAlphaSize = ( translucent ) ? 8 : 0;
		//const EGLint requestDepthSize = ( enableDepthStencil ) ? 24 : 0;
		const EGLint requestDepthSize = ( enableDepthStencil ) ? 16 : 0; // Tegra用
		const EGLint requestStencilSize = ( enableDepthStencil ) ? 8 : 0;
		const EGLint requestSamples = ( enableAntialias ) ? 1 : 0;
		const EGLint attribs[] = {
			EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // GLES2
			EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Window
			EGL_RED_SIZE, requestRedSize, // R8
			EGL_GREEN_SIZE, requestGreenSize, // G8
			EGL_BLUE_SIZE, requestBlueSize, // B8
			EGL_ALPHA_SIZE, requestAlphaSize, // A8
			EGL_DEPTH_SIZE, requestDepthSize, // D24
			EGL_STENCIL_SIZE, requestStencilSize, // S8
			EGL_NONE
		};
		// configゲット
		EGLint numConfigs;
		eglChooseConfig( display, attribs, 0, 0, &numConfigs );
		HARD_ASSERT( ( numConfigs > 0 ) && "No configs match configSpec" );
		EGLConfig* configs = NEW EGLConfig[ numConfigs ];
		eglChooseConfig( display, attribs, configs, numConfigs, &numConfigs );

		EGLConfig bestConfig = 0;
		EGLint msaa = 0x7fff;
		for ( EGLint i = 0; i < numConfigs; ++i ) {
			EGLConfig config = configs[ i ];
#ifndef NDEBUG
			//printConfig( display, config );
#endif
			// depth,stencilチェック
			EGLint d, s;
			eglGetConfigAttrib( display, config, EGL_DEPTH_SIZE, &d );
			eglGetConfigAttrib( display, config, EGL_STENCIL_SIZE, &s );
			if ( d < requestDepthSize || s < requestStencilSize ) {
				continue;
			}
			// MSAA
			eglGetConfigAttrib( display, config, EGL_SAMPLES, &s );
			if ( requestSamples > 0 ) {
				// MSAAありのときは希望より小さいやつはスキップ
				if ( s < requestSamples ) {
					continue;
				}
			} else {
				// MSAAなしの場合はできるだけ小さいものを選択する
				// 等号は含まない。同じものでも次にくるものがリッチなため
				if ( msaa < s ) {
					continue;
				}
				msaa = s;
			}
			// カラー
			EGLint r, g, b, a;
			eglGetConfigAttrib( display, config, EGL_RED_SIZE, &r );
			eglGetConfigAttrib( display, config, EGL_GREEN_SIZE, &g );
			eglGetConfigAttrib( display, config, EGL_BLUE_SIZE, &b );
			eglGetConfigAttrib( display, config, EGL_ALPHA_SIZE, &a );
			if ( r == requestRedSize && g == requestGreenSize && b == requestBlueSize && a == requestAlphaSize ) {
				// breakはしない
				// 次のループのほうがリッチなものが来るため
				bestConfig = config;
				break;
			}
		}
		// 後始末
		SAFE_DELETE_ARRAY( configs );
		return bestConfig;
	}
#ifndef NDEBUG
	static void printConfig( EGLDisplay display, EGLConfig config ) {
		const EGLint attributes[] = {
			EGL_BUFFER_SIZE,
			EGL_ALPHA_SIZE,
			EGL_BLUE_SIZE,
			EGL_GREEN_SIZE,
			EGL_RED_SIZE,
			EGL_DEPTH_SIZE,
			EGL_STENCIL_SIZE,
			EGL_CONFIG_CAVEAT,
			EGL_CONFIG_ID,
			EGL_LEVEL,
			EGL_MAX_PBUFFER_HEIGHT,
			EGL_MAX_PBUFFER_PIXELS,
			EGL_MAX_PBUFFER_WIDTH,
			EGL_NATIVE_RENDERABLE,
			EGL_NATIVE_VISUAL_ID,
			EGL_NATIVE_VISUAL_TYPE,
			0x3030, // EGL_PRESERVED_RESOURCES,
			EGL_SAMPLES,
			EGL_SAMPLE_BUFFERS,
			EGL_SURFACE_TYPE,
			EGL_TRANSPARENT_TYPE,
			EGL_TRANSPARENT_RED_VALUE,
			EGL_TRANSPARENT_GREEN_VALUE,
			EGL_TRANSPARENT_BLUE_VALUE,
			EGL_BIND_TO_TEXTURE_RGB,
			EGL_BIND_TO_TEXTURE_RGBA,
			EGL_MIN_SWAP_INTERVAL,
			EGL_MAX_SWAP_INTERVAL,
			EGL_LUMINANCE_SIZE,
			EGL_ALPHA_MASK_SIZE,
			EGL_COLOR_BUFFER_TYPE,
			EGL_RENDERABLE_TYPE,
			EGL_CONFORMANT
		};
		const char* names[] = {
			"EGL_BUFFER_SIZE",
			"EGL_ALPHA_SIZE",
			"EGL_BLUE_SIZE",
			"EGL_GREEN_SIZE",
			"EGL_RED_SIZE",
			"EGL_DEPTH_SIZE",
			"EGL_STENCIL_SIZE",
			"EGL_CONFIG_CAVEAT",
			"EGL_CONFIG_ID",
			"EGL_LEVEL",
			"EGL_MAX_PBUFFER_HEIGHT",
			"EGL_MAX_PBUFFER_PIXELS",
			"EGL_MAX_PBUFFER_WIDTH",
			"EGL_NATIVE_RENDERABLE",
			"EGL_NATIVE_VISUAL_ID",
			"EGL_NATIVE_VISUAL_TYPE",
			"EGL_PRESERVED_RESOURCES",
			"EGL_SAMPLES",
			"EGL_SAMPLE_BUFFERS",
			"EGL_SURFACE_TYPE",
			"EGL_TRANSPARENT_TYPE",
			"EGL_TRANSPARENT_RED_VALUE",
			"EGL_TRANSPARENT_GREEN_VALUE",
			"EGL_TRANSPARENT_BLUE_VALUE",
			"EGL_BIND_TO_TEXTURE_RGB",
			"EGL_BIND_TO_TEXTURE_RGBA",
			"EGL_MIN_SWAP_INTERVAL",
			"EGL_MAX_SWAP_INTERVAL",
			"EGL_LUMINANCE_SIZE",
			"EGL_ALPHA_MASK_SIZE",
			"EGL_COLOR_BUFFER_TYPE",
			"EGL_RENDERABLE_TYPE",
			"EGL_CONFORMANT"
		};
		const int attrNumber = sizeof( attributes ) / sizeof( EGLint );
		EGLint value;
		LOGI( "EGLConfig  %p\n", config );
		for ( int i = 0; i < attrNumber; ++i ) {
			if ( eglGetConfigAttrib( display, config, attributes[i], &value ) ) {
				LOGI( "  %s: %d\n", names[ i ], value );
			} else {
				while ( eglGetError() != EGL_SUCCESS );
			}
		}
	}
#endif

	// window handle ptr
	android_app* mWindowHandle;

	// egl object
	EGLDisplay mDisplay;
    EGLSurface mSurface;
	EGLContext mContext;
	EGLConfig mConfig;

	// display config
	EGLint mWidth;
	EGLint mHeight;
	bool mCanRender;
	bool mEnableDepthStencil;
	bool mEnableAntialias;
	bool mEnableAnisotropicFilter;
	bool mTranslucent;

	GLbitfield mBitField;

	// buffer
	VertexBuffer::Impl* mCurrentVertexBuffer;
	IndexBuffer::Impl* mCurrentIndexBuffer;
	Texture::Impl* mCurrentTexture;

	// texture filter
	GLint mCurrentMagFilter;
	GLint mCurrentMinFilter;
	GLint mBestMagFilter;
	GLint mBestMinFilter;
	TextureFilter mTextureFilter;
	GLfloat mMaxAnisotropy;

	// matrix
	Matrix4x4 mProjectionViewMatrix;
	Matrix3x4 mWorldMatrix;
	bool mMatricesChanged;

	Vector4 mLightPositionXs;
	Vector4 mLightPositionYs;
	Vector4 mLightPositionZs;
	Vector3 mLightColors[ 4 ];
	float mLightIntensities[ 4 ];
	Vector3 mEyePosition;
	Vector4 mDiffuseColor;
	Vector4 mSpecularColor;
	Vector3 mEmissionColor;
	Vector4 mAmbientColor;
	bool mLightChanged;

	bool mCurrentDepthTest;
	GLboolean mCurrentDepthWrite;
	GLenum mCurrentCullFace;
	GLenum mCurrentSrcBlend;
	GLenum mCurrentDstBlend;

	struct {
		GLint transformMatrix;
		GLint worldMatrix;
		GLint itWorldMatrix;
		GLint diffuse;
		GLint ambient;
		GLint emission;
		GLint specular;
		GLint lightPosX;
		GLint lightPosY;
		GLint lightPosZ;
		GLint eyePos;
		GLint lightColor0;
		GLint lightColor1;
		GLint lightColor2;
		GLint lightColor3;
		GLint texture;
	} mLocation; //!< シェーダ定数へのハンドル

	struct {
		GLint x;
		GLint y;
		GLsizei width;
		GLsizei height;
	} mViewport; //!< ビューポート

	// シェーダプログラム
	GLuint mNoLightingShader;
	GLuint mVertexLightingShader;
	GLuint mPixelLightingShader;

	LightingMode mLightingMode;

	Texture::Impl* mWhiteTexture;

	unsigned mFrameId;

#ifdef TEST_FRAME_BUFFER
	GLuint mFrameBuffer;
	GLuint mRenderBuffer;
	GLuint mDepthTexture;
#endif

	static const char sShaderNoLightingVsSrc[];
	static const char sShaderVertexLightingVsSrc[];
	static const char sShaderPixelLightingVsSrc[];
	static const char sShaderPixelLightingFsSrc[];
	static const char sShaderBasicFsSrc[];
};

extern ManagerImpl* gManagerImpl;

} // namespace Graphics
} // namespace NtEngine

#endif // INCLUDED_NTENGINE_GRAPHICS_MANAGERIMPL_H
